/*
 * This is based off the O3D simpleviewer sample, modified for this
 * window demo.
 *
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// *********************** SimpleViewerWindow **********************************

/***
 * A SimpleViewerWindow is a window that shows a 3d model.
 * @constructor
 * @param {string} path URI of file to load.
 */
function __SimpleViewerWindow(path) {
  this.filePath = path;
}

/**
 * Makes __SimpleViewerWindow inherit from __DraggableWindow
 */
__SimpleViewerWindow.prototype = new __DraggableWindow();

/**
 * Initialized a__SimpleViewerWindow.
 * @param {Object} spec Initialization parameters. See __DraggableWindow.init.
 */
__SimpleViewerWindow.prototype.init = function(spec) {
  this.camera = {
    farPlane: 5000,
    nearPlane: 0.1
  };
  this.camera.target = [0, 0, 0];
  this.camera.eye = [0, 0, 5];
  __DraggableWindow.prototype.init.call(this, spec);
  var pack = g_client.createPack();
  this.appPack = pack;
  this.modelPack = null;
  this.lastRot = g_math.matrix4.identity();
  this.thisRot = g_math.matrix4.identity();
  this.aball = o3djs.arcball.create(this.rect.width, this.rect.height);

  // Set the light at the same position as the camera to create a headlight
  // that illuminates the object straight on.
  this.lightPosParam = this.root.createParam('lightWorldPos', 'ParamFloat3');
  this.updateCamera();
  this.setProjection();
  this.loadFile(this.filePath);
};

/**
 * Unloads any file currentl loaded in the view.
 */
__SimpleViewerWindow.prototype.unloadFile = function() {
  if (this.modelPack) {
    this.modelPack.destory();
    this.modelParent.parent = null;
    this.modelPack = null;
  }
};

/**
 * Closes a __SimpleViewerWindow
 */
__SimpleViewerWindow.prototype.close = function() {
  this.unloadFile();
  this.appPack.destroy();
  __Window.prototype.resizeWindow.close(this, size);
};

/**
 * Sets the projection matrix
 */
__SimpleViewerWindow.prototype.setProjection = function() {
  // Create our projection matrix, with a vertical field of view of 45 degrees
  // a near clipping plane of 0.1 and far clipping plane of 100.
  var fovInRad = 45 * Math.PI / 180
  var nearPlane = this.camera.nearPlane;
  var farPlane = this.camera.farPlane;
  var aspectRatio = this.rect.width / this.rect.height;
  this.setPerspective(
      fovInRad,
      aspectRatio,
      nearPlane,
      farPlane);
};

/**
 * Resizes this window.
 * @param {Point} size where x = width, y = height.
 */
__SimpleViewerWindow.prototype.resizeWindow = function(size) {
  __Window.prototype.resizeWindow.call(this, size);
  this.setProjection();
  // Sets a new area size for arcball.
  this.aball.setAreaSize(size.x, size.y);
};

/**
 * Starts a mouse drag operation.
 * @param {Point} offset offset of mouse.
 */
__SimpleViewerWindow.prototype.onStartDrag = function(offset) {
  var windowPosition = this.getAbsolutePosition();
  this.startPosition = createPoint(offset.x - windowPosition.x,
                                   offset.y - windowPosition.y);
//dump('start: ' + offset.x + ', ' + offset.y + '\n');
  this.lastRot = this.thisRot;
  this.aball.click([this.startPosition.x, this.startPosition.y]);
};

/**
 * Responds to drag events.
 * @param {Point} offset offset of mouse.
 */
__SimpleViewerWindow.prototype.onDrag = function(offset) {
  offset = createPoint(this.startPosition.x + offset.x,
                       this.startPosition.y + offset.y);
//dump('drag : ' + offset.x + ', ' + offset.y + '\n');
  var rotationQuat = this.aball.drag([offset.x, offset.y]);
  var rot_mat = o3djs.quaternions.quaternionToRotation(rotationQuat);
  this.thisRot = g_math.matrix4.mul(this.lastRot, rot_mat);

  var m = this.root.localMatrix;
  g_math.matrix4.setUpper3x3(m, this.thisRot);
  this.root.localMatrix = m;
};

/**
 * Updates the camera (view matrix).
 */
__SimpleViewerWindow.prototype.updateCamera = function() {
  var up = [0, 1, 0];
  this.viewInfo.drawContext.view = g_math.matrix4.lookAt(this.camera.eye,
                                                         this.camera.target,
                                                         up);
  this.lightPosParam.value = this.camera.eye;
};

/**
 * Reponds to mouse scroll events.
 * @param {Object} event Information about the mouse at the time of the event.
 */
__SimpleViewerWindow.prototype.onMouseScroll = function(event) {
dump("ea: " + event.amount + "\n");
  if (event.scrollAmount) {
    var t = 13 / 12;
    if (event.scrollAmount > 0) {
      t = 11 / 12;
    }
    this.camera.eye = g_math.lerpVector(this.camera.target, this.camera.eye, t);
    this.updateCamera();
  }
};

/**
 * Enables or disables input.
 * @param {boolean} enable True = enable, false = disable.
 */
__SimpleViewerWindow.prototype.enableInput = function(enable) {
  // stub.
};

/**
 * Sets the loading status .
 * @param {string} msg A status message.
 */
__SimpleViewerWindow.prototype.setLoadingStatus = function(msg) {
  // stub.
};

/**
 * Prepares a model for rendering.
 */
__SimpleViewerWindow.prototype.prepareModel = function() {
  // Generate draw elements and setup material draw lists.
  o3djs.pack.preparePack(this.modelPack, this.viewInfo);
  var bbox = o3djs.util.getBoundingBoxOfTree(this.root);
  this.camera.target = g_math.lerpVector(bbox.minExtent, bbox.maxExtent, 0.5);
  var diag = g_math.length(g_math.subVector(bbox.minExtent, bbox.maxExtent));
  this.camera.eye = g_math.addVector(this.camera.target, [0, 0, 1.5 * diag]);
  this.camera.nearPlane = diag / 1000;
  this.camera.farPlane = diag * 10;
  this.setProjection();
  this.updateCamera();

  // Manually connect all the materials' lightWorldPos params to the a global
  // light param
  var materials = this.modelPack.getObjectsByClassName('o3d.Material');
  for (var m = 0; m < materials.length; ++m) {
    var material = materials[m];
    var param = material.getParam('lightWorldPos');
    if (param) {
      param.bind(this.lightPosParam);
    }
  }
};

/**
 * Loads a file.
 * @param {string} path URL of file to load.
 * @return {!o3d.Transform} root transform created for loaded file.
 */
__SimpleViewerWindow.prototype.loadFile = function(path) {
  o3djs.dump.dump('load: ' + path + '\n');
  var that = this;
  function callback(pack, parent, exception) {
    that.enableInput(true);
    if (exception) {
      alert('Could not load: ' + path);
      that.setLoadingStatus('loading failed.');
    } else {
      that.setLoadingStatus('loading finished.');
      that.prepareModel();
    }
  }

  this.modelPack = g_client.createPack();

  // Create a new transform for the loaded file
  var parent = this.modelPack.createObject('Transform');
  this.modelParent = parent;
  parent.parent = this.root;

  if (path != null) {
    this.setLoadingStatus('Loading: ' + path);
    this.enableInput(false);
    o3djs.scene.loadScene(g_client,
                          this.modelPack,
                          parent,
                          path,
                          callback);
  }

  return parent;
};

